docker - build
contents
docker build 프로세스는 Dockerfile에 정의된 사람이 읽을 수 있는 명령어와 관련 빌드 컨텍스트를 휴대 가능하고 버전이 지정된 도커 이미지로 변환하는 메커니즘입니다. 전체 프로세스는 도커 데몬(Docker Daemon)에 의해 관리되며, 계층화되고 불변하는 캐싱 시스템에 크게 의존합니다.
1. 빌드 아키텍처: 클라이언트 vs. 데몬
빌드 프로세스는 근본적으로 클라이언트-서버 작업입니다.
- 도커 클라이언트 (
docker build명령어): 사용자의 로컬 머신이나 터미널에서 실행됩니다. 이 클라이언트의 유일한 역할은Dockerfile을 찾고, 빌드 컨텍스트(로컬 파일들)를 패키징하여 데몬에게 스트리밍하는 것입니다. - 도커 데몬 (빌더): 호스트 머신(로컬이든 원격이든)에서 실행되는 백그라운드 프로세스입니다. 스트림을 수신하고 명령어 실행 및 이미지 레이어 생성이라는 실제 작업을 수행합니다.
핵심 단계: 컨텍스트 업로드
docker build의 첫 번째 작업은 빌드 디렉터리(컨텍스트)의 모든 파일을 수집하여 데몬에게 전송하는 것입니다. 이것이 불필요한 파일(.dockerignore 파일로 제어)을 제외하고 최소한의 빌드 컨텍스트를 유지하는 것이 중요한 이유입니다. 크고 불필요한 파일을 전송하는 것은 캐싱 여부와 관계없이 모든 빌드의 시작 시간을 상당히 지연시킵니다.
2. 핵심 메커니즘: 불변 레이어와 캐싱
도커 이미지의 특징은 읽기 전용 레이어 스택으로 구축된다는 것입니다. Dockerfile의 각 명령어는 레이어를 생성합니다.
레이어 생성
- 모든 명령어(
FROM,RUN,COPY,ADD등)는 새롭고 불변하며 읽기 전용인 이미지 레이어를 생성합니다. - 이러링 덕분에 두 이미지가 동일한 기반 이미지에서 구축될 경우 정확히 동일한 하위 레이어를 공유할 수 있으므로 이미지가 매우 효율적입니다.
빌드 캐시 (속도 요소)
도커 데몬은 어떤 명령어를 실행하기 전에, 과거에 실행된 정확히 동일한 명령어 로 생성된 기존 레이어가 로컬 캐시에 있는지 확인합니다.
- 캐시 히트(Cache Hit): 명령어 및 해당 명령어가 참조하는 모든 파일이 변경되지 않았다면, 데몬은 기존 레이어를 재사용하고 해당 명령어의 실행을 건너뜁니다. 이는 후속 빌드를 매우 빠르게 만듭니다.
- 캐시 무효화 (Cache Invalidation): 명령어 자체가 변경되거나,
ADD또는COPY가 참조하는 파일 내용 이 변경되는 순간, 해당 단계의 캐시가 무효화됩니다. 더 중요한 것은, 캐시는 Dockerfile의 모든 후속 단계 에 대해서도 무효화된다는 것입니다.
이는 효율적인 docker build가 명령 순서를 올바르게 지정하여 캐시 히트를 최대화하는 데 전적으로 의존한다는 것을 의미합니다.
3. 단계별 명령어 실행
FROM (기반)
- 역할: 기반 이미지(예:
ubuntu:22.04,node:18-alpine)를 지정합니다. 이는 다른 모든 레이어가 쌓이는 토대가 됩니다. - 캐시: 태그를 변경하면(예:
node:18에서node:20으로) 전체 빌드의 캐시가 무효화됩니다.
RUN (실행기)
- 역할: 이미지 환경 내에서 셸(
bin/sh기본값)을 사용하여 명령을 실행합니다. - 결과:
RUN명령어마다 새로운 레이어를 생성합니다. - 최적화: 각
RUN명령어는 별도의 레이어를 생성하므로, 관련된 명령어(패키지 설치, 업데이트, 정리)를 논리적 AND 연산자(&&)와 역슬래시(\)를 사용하여 하나의RUN명령어로 연결해야 합니다. 이는 결과 레이어 수를 줄이고 캐시 효율성을 높입니다.
# Good: 단 하나의 레이어만 생성
RUN apt-get update && \
apt-get install -y git && \
rm -rf /var/lib/apt/lists/*
COPY / ADD (파일 전송)
- 역할: 빌드 컨텍스트의 파일을 이미지 안으로 복사합니다.
COPY는 동작이 더 투명하므로 일반적으로 선호됩니다.ADD는 원격 URL 지원이나 자동 tar 압축 해제와 같은 추가 기능이 있어 동작 예측이 더 어렵습니다. - 캐시 무효화 위험: 캐시 미스의 가장 흔한 원인입니다. 복사되는 파일 _내용_이 변경되면 해당
COPY명령어의 캐시가 깨지고, 모든 후속 레이어가 다시 빌드되어야 합니다.
# 종속성만 복사하여 npm install 캐시 히트 가능성을 최대화
COPY package.json package-lock.json ./
RUN npm install
# 이 COPY는 소스 코드만 변경될 때 실행됨 (종속성이 변경될 때는 실행되지 않음):
COPY . .
CMD & ENTRYPOINT (시작 메타데이터)
- 역할: 레이어를 생성하지 않고, 이 이미지로 컨테이너를 시작할 때 실행될 명령을 정의하는 메타데이터를 설정합니다.
ENTRYPOINT: 실행 파일을 지정합니다. exec 형식(["실행 파일", "매개변수1"])으로 자주 사용됩니다.CMD:ENTRYPOINT의 기본 인수를 지정합니다.ENTRYPOINT가 정의되지 않은 경우CMD가 실행할 전체 명령을 지정합니다.
4. 고급 최적화: 멀티 스테이지 빌드
멀티 스테이지 빌드는 최종 이미지 크기를 작게 유지하고 보안을 개선하는 모범 사례입니다.
- 문제: 애플리케이션(예: Go 또는 Java 애플리케이션)을 빌드하려면 런타임에는 필요 없는 컴파일러, SDK, 빌드 종속성, 도구가 필요합니다. 이것들을 포함하면 최종 이미지 크기가 부풀려집니다.
- 해결책: 멀티 스테이지 빌드는 여러 개의
FROM문을 사용하여 임시 빌드 단계를 정의합니다. - 작동 방식: 첫 번째 단계("빌더" 단계)를 사용하여 애플리케이션을 컴파일하고 최종 실행 가능한 아티팩트를 생성합니다. 그런 다음 두 번째 단계("런타임" 단계)에서는 최소한의 기본 이미지(예:
scratch또는alpine)에서 시작하여 오직COPY --from=builder명령으로 필요한 단 하나의 실행 파일만 복사합니다. 첫 번째 단계의 모든 빌드 도구는 버려져 최종 이미지 크기가 훨씬 작아지고 보안이 강화됩니다.
# Stage 1: 빌드 (컴파일러 및 도구 포함)
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o /usr/local/bin/app ./cmd/server
# Stage 2: 런타임 (최소 크기)
FROM alpine:latest
# 'builder' 단계에서 최종 실행 파일만 복사
COPY --from=builder /usr/local/bin/app /usr/local/bin/app
ENTRYPOINT ["app"]
5. 모범 사례 요약
| 규칙 | 이유 |
|---|---|
.dockerignore 사용 |
불필요한 파일을 데몬에 보내지 않아 초기 컨텍스트 업로드 속도를 높입니다. |
| 변경 빈도가 낮은 순서대로 레이어 정렬 | RUN apt-get update와 같은 명령어는 캐시 히트를 최대화하기 위해 초기에 배치되어야 합니다. 소스 코드 COPY는 거의 끝에 위치해야 합니다. |
RUN 명령어 연결 |
생성되는 총 레이어 수를 줄여 이미지를 더 작고 빠르게 가져올 수 있습니다. |
| 멀티 스테이지 빌드 사용 | 개발 종속성을 제거하여 최종 이미지 크기와 공격 표면을 획기적으로 줄입니다. |
| 구체적인 태그 사용 | 반복 가능한 빌드를 보장하기 위해 FROM에 node:latest 대신 node:20-alpine과 같은 특정 태그를 사용합니다. |
ADD 대신 COPY 선호 |
COPY는 예측 가능한 동작을 가지며 더 안전하고 투명하다고 간주됩니다. |
도커 이미지 최적화의 두 가지 가장 중요한 기술인 빌드 캐시(Build Cache) 와 멀티 스테이지 빌드(Multi-Stage Builds) 에 대해 자세히 설명해 드립니다. 이 둘은 목적이 다릅니다. 캐시는 속도를 위한 것이고, 멀티 스테이지 빌드는 크기와 보안을 위한 것입니다.
1. 도커 빌드 캐시: 속도와 불변성 ⚡
도커 빌드 캐시는 이전에 성공적으로 빌드된 레이어를 재사용하여 중복 단계를 건너뛰도록 설계된 자동화 메커니즘입니다.
핵심 개념: 불변 레이어와 해싱
Dockerfile의 모든 명령어(예: FROM, RUN, COPY)는 새롭고 불변하며 읽기 전용인 레이어를 생성합니다. 도커는 이 레이어들을 식별하기 위해 콘텐츠 해싱을 사용합니다.
- 명령어 해싱: 도커는 명령어 텍스트(예:
RUN apt-get install -y git)를 가져와 고유한 해시를 계산합니다. - 컨텍스트 해싱:
COPY및ADD명령어의 경우, 도커는 복사되는 _소스 파일_의 콘텐츠를 기반으로 해시를 계산합니다. - 캐시 히트: 도커는 어떤 줄을 실행하기 전에, 현재 줄과 명령어 해시 및 컨텍스트 해시가 일치하는 레이어가 로컬 저장소에 존재하는지 확인합니다. 이 레이어가 발견되면, 도커는 해당 명령을 다시 실행하는 대신 기존 레이어를 재사용합니다. 이는 거의 즉각적입니다.
캐시 무효화 (핵심 규칙)
캐시는 순차적으로 작동합니다. 도커가 캐시 미스를 유발하는 명령어를 만나는 순간, 해당 명령어의 캐시가 무효화되고, 이후의 모든 명령어에 대해서도 캐시가 무효화됩니다.
COPY/ADD무효화:COPY명령에 나열된 _단 하나의 파일_이라도 내용이 변경되면, 해당 단계의 캐시는 깨집니다. 이는 캐시 무효화의 가장 흔한 원인입니다.RUN무효화:RUN명령어의 텍스트 자체가 변경되면(예:apt-get install -y vim을apt-get install -y htop으로 변경), 명령어의 해시가 바뀌어 캐시가 깨집니다.
모범 사례: 레이어 순서 지정
캐시 히트를 최대화하기 위한 황금률은 가장 적게 변경되는 명령어를 Dockerfile의 상단에, 가장 자주 변경되는 명령어를 하단에 배치하는 것입니다.
| 위치 | 명령어 | 이유 |
|---|---|---|
| 상단 | FROM, RUN apt update |
기본 OS 업데이트 시에만 변경됩니다 (변경 빈도 낮음). |
| 중단 | RUN npm install / RUN go build |
종속성이 변경될 때 변경됩니다 (변경 빈도 중간). |
| 하단 | COPY . . (소스 코드) |
거의 모든 커밋에서 변경됩니다 (변경 빈도 매우 높음). |
2. 멀티 스테이지 빌드: 크기와 보안 📦
멀티 스테이지 빌드는 단일 Dockerfile 내에서 여러 개의 FROM 문을 사용할 수 있게 하는 최신 모범 사례입니다. 이는 최종 이미지에서 "빌드 도구 비대화" 문제를 해결합니다.
"빌드 도구 비대화" 문제
많은 애플리케이션(예: Go, Java, React로 작성된 애플리케이션)은 최종 실행 파일이나 정적 아티팩트를 빌드하는 데에만 컴파일러, SDK, 개발 라이브러리와 같은 대용량 종속성을 필요로 합니다. 이들을 모두 포함하면 최종 이미지 크기가 불필요하게 커집니다.
- 결과:
- 이미지 크기 증가 (Pull/Push 속도 저하).
- 공격 표면 증가 (더 많은 도구는 더 많은 잠재적 보안 취약점을 의미).
해결책: 명명된 스테이지 사용
멀티 스테이지 빌드는 AS 키워드를 사용하여 임시 스테이지를 정의합니다. 최종적으로 필요한 아티팩트(실행 파일 또는 컴파일된 파일)만 최소한의 런타임 스테이지로 복사됩니다.
문법 및 워크플로우
- 스테이지 1: 빌더 (무거운 작업): 이 스테이지는 모든 빌드 종속성이 포함된 큰 기반 이미지를 사용합니다 (예:
golang:1.21또는node:20-alpine).
# Stage 1: 'builder'로 명명
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
# Go 애플리케이션을 단일 실행 파일로 컴파일
RUN go build -o /usr/local/bin/my-app ./cmd/server
- 스테이지 2: 런타임 (최소 아티팩트 전송): 이 스테이지는 작고 가벼운 기반 이미지(예:
scratch,alpine또는-slim버전)로 새로 시작합니다. 결정적으로,COPY --from명령을 사용하여 이전 스테이지에서 필요한 아티팩트만 복사합니다.
# Stage 2: 런타임
FROM alpine:latest
WORKDIR /usr/local/bin
# 'builder' 스테이지에서 컴파일된 실행 파일 *만* 복사
COPY --from=builder /usr/local/bin/my-app .
ENTRYPOINT ["/usr/local/bin/my-app"]
결합된 영향
이 두 기능의 조합은 강력합니다.
- 빌드 캐시는 개발 반복 과정에서 초기 단계(예: 종속성 설치)의 속도를 높입니다.
- 멀티 스테이지 빌드는 빌드가 완료된 후 최종 결과물이 깨끗하고, 작으며(종종 크기를 90%까지 감소), 배포 속도가 빠르도록 보장합니다.
references